package com.varian.security.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.map.MapUtil;
import com.varian.redis.util.RedisUtil;
import com.varian.security.constant.SecurityConstant;
import com.varian.security.model.LoginUser;
import lombok.experimental.UtilityClass;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;

import java.time.Duration;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @author ben
 * @since 2024/6/24
 */
@UtilityClass
public class SecurityUtil {

    public void cacheLoginUser(LoginUser loginUser, Duration refreshTokenTimeToLive) {
        String key = SecurityConstant.KEY_LOGIN_USER.format(loginUser.getUserId());
        doCacheLoginUser(key, loginUser);
        RedisUtil.expire(key, refreshTokenTimeToLive.getSeconds(), TimeUnit.SECONDS);
    }

    private void doCacheLoginUser(String key, LoginUser loginUser) {
        Map<String, Object> map = MapUtil.<String, Object>builder()
                .put(SecurityConstant.USER_ID, loginUser.getUserId())
                .put(SecurityConstant.USERNAME, loginUser.getUsername())
                .put(SecurityConstant.NICKNAME, loginUser.getNickname())
                .put(SecurityConstant.EXTRA_MAP, loginUser.getExtraMap())
                .put(SecurityConstant.AVATAR, loginUser.getAvatar())
                .put(SecurityConstant.ENABLE, loginUser.getEnable())
                .put(SecurityConstant.AUTHORITIES, CollUtil.map(loginUser.getAuthorities(), GrantedAuthority::getAuthority, true))
                .build();
        RedisUtil.Hash.putAll(key, map);
    }

    @SuppressWarnings("unchecked")
    public LoginUser getLoginUserByCache(Long userId) {
        String key = SecurityConstant.KEY_LOGIN_USER.format(userId);
        Assert.isTrue(RedisUtil.hasKey(key), () -> new InvalidBearerTokenException("无效令牌"));
        Map<String, Object> map = RedisUtil.Hash.entries(key);
        String username = MapUtil.getStr(map, SecurityConstant.USERNAME);
        String nickname = MapUtil.getStr(map, SecurityConstant.NICKNAME);
        String avatar = MapUtil.getStr(map, SecurityConstant.AVATAR);
        Map<String, Object> extraMap = MapUtil.get(map, SecurityConstant.EXTRA_MAP, new TypeReference<>() {
        });
        Boolean enable = MapUtil.getBool(map, SecurityConstant.ENABLE);
        List<String> authorityList = (List<String>) map.getOrDefault(SecurityConstant.AUTHORITIES, Collections.emptyList());
        return new LoginUser(userId, username, avatar, nickname, extraMap, enable, authorityList);
    }

    public Authentication getAuthentication() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    public boolean isAuthentication() {
        Authentication authentication = getAuthentication();
        return Optional.ofNullable(authentication).isPresent() && authentication.isAuthenticated();
    }

    public LoginUser getLoginUser() {
        Authentication authentication = getAuthentication();
        return Optional.ofNullable(authentication)
                .map(Authentication::getPrincipal)
                .filter(LoginUser.class::isInstance)
                .map(LoginUser.class::cast)
                .orElse(null);
    }

    public Long getUserId() {
        return Optional.ofNullable(getLoginUser())
                .map(LoginUser::getUserId)
                .orElse(null);
    }

    public Long getUserIdOnSafe() {
        Long userId = getUserId();
        return userId == null ? 0L : userId;
    }

    public boolean isAdmin() {
        return Optional.ofNullable(getLoginUser())
                .map(LoginUser::getAuthorities)
                .map(authorities -> authorities.stream().anyMatch(authority -> SecurityConstant.ADMIN_ROLE.equals(authority.getAuthority())))
                .orElse(false);
    }

    public List<String> getPermissions() {
        return Optional.ofNullable(getLoginUser())
                .map(LoginUser::getAuthorities)
                .map(authorities -> authorities.stream().map(GrantedAuthority::getAuthority).toList())
                .orElse(Collections.emptyList());
    }
}
